#include "mvc/model/model.h"
#include "mvc/view/IAbstract_observer.h"
#include "mvc/model/aircrafts/uavs/uav_joined_wings.h"
#include "mvc/model/aircrafts/conventional/conventional_wings.h"
#include "common/log/QsLog.h"
#include "common/settings.h"
#include "common/modules.h"

#include <iostream>

using std::cout;
using std::endl;

Model::Model()
{
    initialize_state();
    c_project = NULL;
}

Model::~Model()
{

}

/**
  * Subscribed a new observer
  */
void Model::subscribe(IAbstract_observer *observer)
{
    observers.append(observer);
}

/**
  *
  */
void Model::unsubscribe(IAbstract_observer *observer)
{
    cout << "TODO: unsubscribe" << endl;
}

/**
  * Initialize the model state
  */
void Model::initialize_state()
{
    _state.cdrawed_configuration = -1;
    _state.draw_simulation = false;
    _state.st_scale = 0;
    _state.total_steps = 0;
    _state.st_step = 0;
    _state.c_texture = true;
    _state.s_texture = false;
    _state.to_load_simulation = false;
}

// PUBLIC METHODS

/**
  * Create the project to keep information of a new project
  */
void Model::create_project(const QString &file_directory, const QString &filename)
{
    //cout << "TODO: Model.create_project" << endl;
    c_project = new Project(file_directory, filename);
}

/**
  * Create the project to keep information of a existing project
  */
void Model::open_project(const QString &project_name, int nconfig)
{
    QString fdir = QString(project_name).remove(project_name.lastIndexOf("/"), project_name.size());
    QString file = QString(project_name).remove(0, project_name.lastIndexOf("/") + 1);

    c_project = new Project(fdir, file, EXISTING_PROJECT, nconfig);
}

/**
  * return the curren project information
  */
const Project& Model::current_project() const
{
    return *c_project;
}


/**
  * Make the all configurations of the current project
  * @param configs_params - a vector of the parameters of all configurations.
  * @param airfoils -  a vector of the airfoils of the configurations. All airfoils are the same for
  * each configuration.
  * @param module - the configuration module.
  */
void Model::make_configurations(QVector<Parameters *> configs_params, Airfoils_curve_map * airfoils, int module)
{
    QLOG_INFO() << "Makes " << configs_params.size() << " configurations to module: " << module;
    QLOG_INFO() << "Using " << airfoils->size() << " airfoils";
    configurations.resize(configs_params.size());
    loaded_simulations.resize(configs_params.size());
    configuration_simulated.resize(configs_params.size());

    for (int i = 0; i < configs_params.size(); ++i)
    {
        if(module == get_module_value(uavs_jw_module))
            configurations[i] = new Uav_Joined_Wings(i + 1, configs_params[i], airfoils);
        else if(module == get_module_value(conventional_w_module))
            configurations[i] = new Conventional_Wings(i + 1, configs_params[i], airfoils);
        else if(module == get_module_value(bwb_module))
            QLOG_ERROR() << "Make bwb";
    }
}

/**
  * Load the information necesary to begin the simulation.
  * @param configuration - the number of configuration
  * @param total_steps - the total steps of the simulation.
  * @param scale - the scale value to all points of the simulation.
  */
void Model::load_simulation(int configuration, int total_steps, double scale)
{
//    QLOG_INFO() << "TODO: Model::load_simulation";

    _state.to_load_simulation = not loaded_simulations.at(configuration - 1);
    _state.total_steps = total_steps;
    _state.st_scale = scale;

    for (int i = 0; i < observers.size(); ++i)
        observers[i]->on_update(configurations[configuration - 1]);
}

/**
  * add the simulation information to a specified configuration.
  * @param config - the number of configuration
  * @param zones - the enterely simulation information divided by time zones.
  */
void Model::add_simulation_zones(int config, QVector<time_zone*> zones)
{
    QLOG_INFO() << "Add " << zones.size() << " zones to configuration: " << config;

    configurations[config - 1]->load_simulation(zones);
    _state.to_load_simulation = false;
    loaded_simulations.setBit(config - 1, true);
}

/**
  *
  */
void Model::clean_model()
{
    if(c_project)
        delete c_project;
    configurations.resize(0);
    loaded_simulations.fill(false, 0, loaded_simulations.size() - 1);
    initialize_state();
}

/**
 * @brief Model::get_configuration
 * @param index
 * @return
 */
const Configuration &Model::get_configuration(int index) const
{
    if(index < 1 || index > configurations.size() + 1)
        throw Non_Existent_Geometry();

    return *configurations.at(index - 1);
}

/**
  * Needs to begin draw the configuration
  * @param config - the number of configuration to draw
  * @param s_step - the number of step of the simulation to draw
  * @param scale - the scale number to the points of the simulation.
  */
void Model::draw_configuration(int config, int s_step, float scale) throw (Non_Existent_Geometry)
{
//    cout << "TODO: Model.draw_configuration" << endl;
    if(config < 1 || config > configurations.size() + 1)
        throw Non_Existent_Geometry();

    _state.draw_simulation = (s_step > 0)? true : false;

    _state.st_scale = scale;
    _state.st_step = s_step;

    QLOG_INFO() << "Need to draws configuration: " << config;

    if(_state.cdrawed_configuration != config)
    {
        if(_state.cdrawed_configuration > 0)
        {
            configurations[_state.cdrawed_configuration - 1]->clear();
            loaded_simulations.clearBit(_state.cdrawed_configuration - 1);
        }

        if(_state.draw_simulation)
            _state.to_load_simulation = not loaded_simulations.at(config - 1);

        if(not configurations[config - 1]->is_generated())
            configurations[config - 1]->generate_geometry();

        _state.cdrawed_configuration = config;

        _state.c_path = c_project->get_config_dir(config);

        QLOG_INFO() << "Model changed!!";
        QLOG_INFO() << "With state: ";
        QLOG_INFO() << "Draw simulation: " << _state.draw_simulation;
        QLOG_INFO() << "Scale value: " << _state.st_scale;
        QLOG_INFO() << "Total steps: " << _state.total_steps;
        QLOG_INFO() << "Current step: " << _state.st_step;
        QLOG_INFO() << "Geometry with texture: " << _state.c_texture;
        QLOG_INFO() << "Simulation with: " << _state.s_texture;
        QLOG_INFO() << "Need to load simulation: " << _state.to_load_simulation;
        QLOG_INFO() << "Last drawn configuration: " << _state.cdrawed_configuration;
        QLOG_INFO() << "Current path: " << _state.c_path;
    }

    for (int i = 0; i < observers.size(); ++i)
        observers[i]->on_update(configurations[config - 1]);
}

/**
  * genereate the all configurations.
  */
void Model::generate_configurations()
{
    //cout << "TODO: Model.generate_configurations" << endl;
    for(int i = 0; i < configurations.size(); i++)
        for(int j = 0; j < observers.size(); j++)
        {
            _state.c_path = c_project->get_config_dir(i + 1);
            configurations[i]->generate_geometry();
            observers.at(j)->on_update(configurations.at(i));
        }
}

/**
 * @brief Model::c_simulated
 * @param configuration
 */
void Model::c_simulated(int configuration)
{
    configuration_simulated.setBit(configuration - 1, true);
}

/**
 * @brief Model::is_allc_simulated
 * @return
 */
bool Model::is_allc_simulated()
{
    bool is_all_simulated = true;

    for(int i = 0; i < configuration_simulated.size() && is_all_simulated; i++)
        is_all_simulated = is_all_simulated && configuration_simulated.at(i);

    return is_all_simulated;
}

/**
  * returns true if all configurations are generated.
  */
bool Model::is_allc_generated()
{
    bool is_all_generated = true;

    for(int i = 0; i < configurations.size() && is_all_generated; i++)
        is_all_generated = is_all_generated && configurations.at(i)->is_generated();

    return is_all_generated;
}
